/*--------------------------------------------------------------------------
 * gin_private.h
 *	  postgres 倒排索引访问方法实现的头文件
 *
 *	Copyright (c) 2006-2022, PostgreSQL Global Development Group
 *
 *	src/include/access/gin_private.h
 *--------------------------------------------------------------------------
 */
#ifndef GIN_PRIVATE_H
#define GIN_PRIVATE_H

#include "access/amapi.h"
#include "access/gin.h"
#include "access/ginblock.h"
#include "access/itup.h"
#include "catalog/pg_am_d.h"
#include "fmgr.h"
#include "lib/rbtree.h"
#include "storage/bufmgr.h"

/*
 * GIN 的 reloptions 的存储类型
 */
typedef struct GinOptions
{
	int32		vl_len_;		/* varlena 头部（请勿直接触摸！） */
	bool		useFastUpdate;	/* 使用快速更新？ */
	int			pendingListCleanupSize; /* 待处理列表的最大大小 */
} GinOptions;

#define GIN_DEFAULT_USE_FASTUPDATE	true
#define GinGetUseFastUpdate(relation) \
	(AssertMacro(relation->rd_rel->relkind == RELKIND_INDEX && \
				 relation->rd_rel->relam == GIN_AM_OID), \
	 (relation)->rd_options ? \
	 ((GinOptions *) (relation)->rd_options)->useFastUpdate : GIN_DEFAULT_USE_FASTUPDATE)
#define GinGetPendingListCleanupSize(relation) \
	(AssertMacro(relation->rd_rel->relkind == RELKIND_INDEX && \
				 relation->rd_rel->relam == GIN_AM_OID), \
	 (relation)->rd_options && \
	 ((GinOptions *) (relation)->rd_options)->pendingListCleanupSize != -1 ? \
	 ((GinOptions *) (relation)->rd_options)->pendingListCleanupSize : \
	 gin_pending_list_limit)


/* 用于缓冲区锁/解锁操作的宏 */
#define GIN_UNLOCK	BUFFER_LOCK_UNLOCK
#define GIN_SHARE	BUFFER_LOCK_SHARE
#define GIN_EXCLUSIVE  BUFFER_LOCK_EXCLUSIVE


/*
 * GinState: 描述正在处理的索引的工作数据结构
 */
typedef struct GinState
{
	Relation	index;
	bool		oneCol;			/* 如果是单列索引，则为 true */

	/*
	 * origTupdesc 是索引的名义元组描述符，即，第 i 个
	 * 属性显示第 i 个索引列的键类型（不是输入数据类型！）。
	 * 在单列索引中，这描述了实际的叶子索引元组。
	 * 在多列索引中，实际的叶子元组包含一个 smallint 列编号，
	 * 后跟该列适当类型的键数据。我们设置 tupdesc[i] 来描述
	 * 第 i 列的索引元组的实际行类型，即 (int2, keytype)。
	 * 请注意，在任何情况下，叶子元组包含的数 据比
	 * TupleDesc 所知道的更多；详细信息请参见 access/gin/README。
	 */
	TupleDesc	origTupdesc;
	TupleDesc	tupdesc[INDEX_MAX_KEYS];

	/*
	 * 每个索引列的 opclass 支持函数
	 */
	FmgrInfo	compareFn[INDEX_MAX_KEYS];
	FmgrInfo	extractValueFn[INDEX_MAX_KEYS];
	FmgrInfo	extractQueryFn[INDEX_MAX_KEYS];
	FmgrInfo	consistentFn[INDEX_MAX_KEYS];
	FmgrInfo	triConsistentFn[INDEX_MAX_KEYS];
	FmgrInfo	comparePartialFn[INDEX_MAX_KEYS];	/* 可选方法 */
	/* 如果 comparePartialFn[i] 有效，则 canPartialMatch[i] 为 true */
	bool		canPartialMatch[INDEX_MAX_KEYS];
	/* 传递给支持函数的排序规则 */
	Oid			supportCollation[INDEX_MAX_KEYS];
} GinState;


/* ginutil.c */
extern bytea *ginoptions(Datum reloptions, bool validate);
extern void initGinState(GinState *state, Relation index);
extern Buffer GinNewBuffer(Relation index);
extern void GinInitBuffer(Buffer b, uint32 f);
extern void GinInitPage(Page page, uint32 f, Size pageSize);
extern void GinInitMetabuffer(Buffer b);
extern int	ginCompareEntries(GinState *ginstate, OffsetNumber attnum,
							  Datum a, GinNullCategory categorya,
							  Datum b, GinNullCategory categoryb);
extern int	ginCompareAttEntries(GinState *ginstate,
								 OffsetNumber attnuma, Datum a, GinNullCategory categorya,
								 OffsetNumber attnumb, Datum b, GinNullCategory categoryb);
extern Datum *ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
								Datum value, bool isNull,
								int32 *nentries, GinNullCategory **categories);

extern OffsetNumber gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple);
extern Datum gintuple_get_key(GinState *ginstate, IndexTuple tuple,
							  GinNullCategory *category);

/* gininsert.c */
extern IndexBuildResult *ginbuild(Relation heap, Relation index,
								  struct IndexInfo *indexInfo);
extern void ginbuildempty(Relation index);
extern bool gininsert(Relation index, Datum *values, bool *isnull,
					  ItemPointer ht_ctid, Relation heapRel,
					  IndexUniqueCheck checkUnique,
					  bool indexUnchanged,
					  struct IndexInfo *indexInfo);
extern void ginEntryInsert(GinState *ginstate,
						   OffsetNumber attnum, Datum key, GinNullCategory category,
						   ItemPointerData *items, uint32 nitem,
						   GinStatsData *buildStats);

/* ginbtree.c */

typedef struct GinBtreeStack
{
	BlockNumber blkno;
	Buffer		buffer;
	OffsetNumber off;
	ItemPointerData iptr;
	/* predictNumber 包含当前级别上预测的页面数 */
	uint32		predictNumber;
	struct GinBtreeStack *parent;
} GinBtreeStack;

typedef struct GinBtreeData *GinBtree;

/* GinBtreeData.beginPlaceToPage 方法的返回代码 */
typedef enum
{
	GPTP_NO_WORK,
	GPTP_INSERT,
	GPTP_SPLIT
} GinPlaceToPageRC;

typedef struct GinBtreeData
{
	/* 搜索方法 */
	BlockNumber (*findChildPage) (GinBtree, GinBtreeStack *);
	BlockNumber (*getLeftMostChild) (GinBtree, Page);
	bool		(*isMoveRight) (GinBtree, Page);
	bool		(*findItem) (GinBtree, GinBtreeStack *);

	/* 插入方法 */
	OffsetNumber (*findChildPtr) (GinBtree, Page, BlockNumber, OffsetNumber);
	GinPlaceToPageRC (*beginPlaceToPage) (GinBtree, Buffer, GinBtreeStack *, void *, BlockNumber, void **, Page *, Page *);
	void		(*execPlaceToPage) (GinBtree, Buffer, GinBtreeStack *, void *, BlockNumber, void *);
	void	   *(*prepareDownlink) (GinBtree, Buffer);
	void		(*fillRoot) (GinBtree, Page, BlockNumber, Page, BlockNumber, Page);

	bool		isData;

	Relation	index;
	BlockNumber rootBlkno;
	GinState   *ginstate;		/* 在数据扫描中无效 */
	bool		fullScan;
	bool		isBuild;

	/* 条目树的搜索键 */
	OffsetNumber entryAttnum;
	Datum		entryKey;
	GinNullCategory entryCategory;

	/* 数据树（发布树）的搜索键 */
	ItemPointerData itemptr;
} GinBtreeData;

/* 这表示要插入到条目树的元组。 */
typedef struct
{
	IndexTuple	entry;			/* 要插入的元组 */
	bool		isDelete;		/* 是否在同一偏移量删除旧元组？ */
} GinBtreeEntryInsertData;

/*
 * 这表示要插入到数据（发布树）叶页面的项指针或多个项指针
 */
typedef struct
{
	ItemPointerData *items;
	uint32		nitem;
	uint32		curitem;
} GinBtreeDataLeafInsertData;

/*
 * 对于内部数据（发布树）页面，插入有效负载是一个
 * PostingItem
 */

extern GinBtreeStack *ginFindLeafPage(GinBtree btree, bool searchMode,
									  bool rootConflictCheck, Snapshot snapshot);
extern Buffer ginStepRight(Buffer buffer, Relation index, int lockmode);
extern void freeGinBtreeStack(GinBtreeStack *stack);
extern void ginInsertValue(GinBtree btree, GinBtreeStack *stack,
						   void *insertdata, GinStatsData *buildStats);

/* ginentrypage.c */
extern IndexTuple GinFormTuple(GinState *ginstate,
							   OffsetNumber attnum, Datum key, GinNullCategory category,
							   Pointer data, Size dataSize, int nipd, bool errorTooBig);
extern void ginPrepareEntryScan(GinBtree btree, OffsetNumber attnum,
								Datum key, GinNullCategory category,
								GinState *ginstate);
extern void ginEntryFillRoot(GinBtree btree, Page root, BlockNumber lblkno, Page lpage, BlockNumber rblkno, Page rpage);
extern ItemPointer ginReadTuple(GinState *ginstate, OffsetNumber attnum,
								IndexTuple itup, int *nitems);

/* gindatapage.c */
extern ItemPointer GinDataLeafPageGetItems(Page page, int *nitems, ItemPointerData advancePast);
extern int	GinDataLeafPageGetItemsToTbm(Page page, TIDBitmap *tbm);
extern BlockNumber createPostingTree(Relation index,
									 ItemPointerData *items, uint32 nitems,
									 GinStatsData *buildStats, Buffer entrybuffer);
extern void GinDataPageAddPostingItem(Page page, PostingItem *data, OffsetNumber offset);
extern void GinPageDeletePostingItem(Page page, OffsetNumber offset);
extern void ginInsertItemPointers(Relation index, BlockNumber rootBlkno,
								  ItemPointerData *items, uint32 nitem,
								  GinStatsData *buildStats);
extern GinBtreeStack *ginScanBeginPostingTree(GinBtree btree, Relation index, BlockNumber rootBlkno, Snapshot snapshot);
extern void ginDataFillRoot(GinBtree btree, Page root, BlockNumber lblkno, Page lpage, BlockNumber rblkno, Page rpage);

/*
 * 这是在 ginvacuum.c 中声明的，但在 ginVacuumItemPointers
 * 和 ginVacuumPostingTreeLeaf 之间传递，并作为一个不透明的结构，
 * 所以我们需要对其进行前向声明。
 */
typedef struct GinVacuumState GinVacuumState;

extern void ginVacuumPostingTreeLeaf(Relation rel, Buffer buf, GinVacuumState *gvs);

/* ginscan.c */

/*
 * GinScanKeyData 描述了一个单一的 GIN 索引限定符表达式。
 *
 * 从每个限定表达式中，我们提取一个或多个特定的索引搜索
 * 条件，这些条件由 GinScanEntryData 表示。请求相同搜索条件的
 * 多个限定表达式是非常可能的，这种情况下我们合并这些条件，
 * 以只拥有一个唯一的 GinScanEntry —— 这在处理全索引扫描
 * 条目时特别重要，以提高效率。因此，可以有多个
 * GinScanKeyData.scanEntry 指针指向同一个 GinScanEntryData。
 *
 * 在每个 GinScanKeyData 中，nentries 是实际的条目数量，
 * 而 nuserentries 是 extractQueryFn 返回的数量（这是我们
 * 报告给 consistentFn 的）。 "用户" 条目必须首先出现。
 */
typedef struct GinScanKeyData *GinScanKey;

typedef struct GinScanEntryData *GinScanEntry;

typedef struct GinScanKeyData
{
	/* scanEntry[] 中的真实条目数量（始终 > 0） */
	uint32		nentries;
	/* extractQueryFn 和 consistentFn 知道的条目数量 */
	uint32		nuserentries;

	/* GinScanEntry 指针的数组，每个提取的搜索条件一个指针 */
	GinScanEntry *scanEntry;

	/*
	 * 至少必须存在 requiredEntries 中的一项条目，才能使
	 * 一个元组与整体限定条件匹配。
	 *
	 * additionalEntries 包含 consistent 函数判断一个项目
	 * 是否匹配所需的条目，但不足以满足限定条件而不需要
	 * requiredEntries 的条目。
	 */
	GinScanEntry *requiredEntries;
	int			nrequired;
	GinScanEntry *additionalEntries;
	int			nadditional;

	/* check 标志的数组，报告给 consistentFn */
	GinTernaryValue *entryRes;
	bool		(*boolConsistentFn) (GinScanKey key);
	GinTernaryValue (*triConsistentFn) (GinScanKey key);
	FmgrInfo   *consistentFmgrInfo;
	FmgrInfo   *triConsistentFmgrInfo;
	Oid			collation;

	/* 调用 consistentFn 所需的其他数据 */
	Datum		query;
	/* 注意：这三个数组仅有 nuserentries 个元素！ */
	Datum	   *queryValues;
	GinNullCategory *queryCategories;
	Pointer    *extra_data;
	StrategyNumber strategy;
	int32		searchMode;
	OffsetNumber attnum;

	/*
	 * 一个 excludeOnly 扫描键无法枚举所有匹配的元组。
	 * 也就是说，为了在语义上自洽，它需要有一个
	 * GIN_CAT_EMPTY_QUERY scanEntry，但它没有。这样的键仍然可以用来
	 * 过滤其他扫描键返回的元组，所以只要对
	 * 搜索所考虑的每个索引属性有至少一个非 excludeOnly 扫描键，
	 * 我们就能得到正确的答案。出于效率考量，
	 * 我们不希望有不必要的 GIN_CAT_EMPTY_QUERY 条目，
	 * 所以只有在没有其他非 excludeOnly 扫描键时，
	 * 我们才会将 excludeOnly 扫描键转换为非 excludeOnly
	 * （通过添加一个 GIN_CAT_EMPTY_QUERY scanEntry）。
	 */
	bool		excludeOnly;

	/*
	 * 匹配状态数据。curItem 是最近测试过的 TID
	 * （可能是一个损失页面指针）。curItemMatches 为 true
	 * 如果它通过了 consistentFn 测试；如果是这样，recheckCurItem
	 * 是重检查标志。isFinished 表示所有输入条目流已完成，
	 * 因此此键不能成功匹配任何后续 TID。
	 */
	ItemPointerData curItem;
	bool		curItemMatches;
	bool		recheckCurItem;
	bool		isFinished;
}			GinScanKeyData;

typedef struct GinScanEntryData
{
	/* 从 extractQueryFn 提取的查询键和其他信息 */
	Datum		queryKey;
	GinNullCategory queryCategory;
	bool		isPartialMatch;
	Pointer		extra_data;
	StrategyNumber strategy;
	int32		searchMode;
	OffsetNumber attnum;

	/* 当前在发帖树中的页面 */
	Buffer		buffer;

	/* 当前指向堆的 ItemPointer */
	ItemPointerData curItem;

	/* 对于部分匹配或全扫描查询，我们在这里累积所有 TID */
	TIDBitmap  *matchBitmap;
	TBMIterator *matchIterator;
	TBMIterateResult *matchResult;

	/* 用于发帖列表和发帖树中的一个页面 */
	ItemPointerData *list;
	int			nlist;
	OffsetNumber offset;

	bool		isFinished;
	bool		reduceResult;
	uint32		predictNumberResult;
	GinBtreeData btree;
}			GinScanEntryData;

typedef struct GinScanOpaqueData
{
	MemoryContext tempCtx;
	GinState	ginstate;

	GinScanKey	keys;			/* 每个扫描限定符表达式一个 */
	uint32		nkeys;

	GinScanEntry *entries;		/* 每个索引搜索条件一个 */
	uint32		totalentries;
	uint32		allocentries;	/* entries[] 的分配长度 */

	MemoryContext keyCtx;		/* 用于保存键和条目数据 */

	bool		isVoidRes;		/* 如果查询不可满足则为 true */
} GinScanOpaqueData;

typedef GinScanOpaqueData *GinScanOpaque;

extern IndexScanDesc ginbeginscan(Relation rel, int nkeys, int norderbys);
extern void ginendscan(IndexScanDesc scan);
extern void ginrescan(IndexScanDesc scan, ScanKey key, int nscankeys,
					  ScanKey orderbys, int norderbys);
extern void ginNewScanKey(IndexScanDesc scan);
extern void ginFreeScanKeys(GinScanOpaque so);

/* ginget.c */
extern int64 gingetbitmap(IndexScanDesc scan, TIDBitmap *tbm);

/* ginlogic.c */
extern void ginInitConsistentFunction(GinState *ginstate, GinScanKey key);

/* ginvacuum.c */
extern IndexBulkDeleteResult *ginbulkdelete(IndexVacuumInfo *info,
											IndexBulkDeleteResult *stats,
											IndexBulkDeleteCallback callback,
											void *callback_state);
extern IndexBulkDeleteResult *ginvacuumcleanup(IndexVacuumInfo *info,
											   IndexBulkDeleteResult *stats);
extern ItemPointer ginVacuumItemPointers(GinVacuumState *gvs,
										 ItemPointerData *items, int nitem, int *nremaining);

/* ginvalidate.c */
extern bool ginvalidate(Oid opclassoid);
extern void ginadjustmembers(Oid opfamilyoid,
							 Oid opclassoid,
							 List *operators,
							 List *functions);

/* ginbulk.c */
typedef struct GinEntryAccumulator
{
	RBTNode		rbtnode;
	Datum		key;
	GinNullCategory category;
	OffsetNumber attnum;
	bool		shouldSort;
	ItemPointerData *list;
	uint32		maxcount;		/* list[] 的分配大小 */
	uint32		count;			/* list[] 当前条目数量 */
} GinEntryAccumulator;

typedef struct
{
	GinState   *ginstate;
	Size		allocatedMemory;
	GinEntryAccumulator *entryallocator;
	uint32		eas_used;
	RBTree	   *tree;
	RBTreeIterator tree_walk;
} BuildAccumulator;

extern void ginInitBA(BuildAccumulator *accum);
extern void ginInsertBAEntries(BuildAccumulator *accum,
							   ItemPointer heapptr, OffsetNumber attnum,
							   Datum *entries, GinNullCategory *categories,
							   int32 nentries);
extern void ginBeginBAScan(BuildAccumulator *accum);
extern ItemPointerData *ginGetBAEntry(BuildAccumulator *accum,
									  OffsetNumber *attnum, Datum *key, GinNullCategory *category,
									  uint32 *n);

/* ginfast.c */

typedef struct GinTupleCollector
{
	IndexTuple *tuples;
	uint32		ntuples;
	uint32		lentuples;
	uint32		sumsize;
} GinTupleCollector;

extern void ginHeapTupleFastInsert(GinState *ginstate,
								   GinTupleCollector *collector);
extern void ginHeapTupleFastCollect(GinState *ginstate,
									GinTupleCollector *collector,
									OffsetNumber attnum, Datum value, bool isNull,
									ItemPointer ht_ctid);
extern void ginInsertCleanup(GinState *ginstate, bool full_clean,
							 bool fill_fsm, bool forceCleanup, IndexBulkDeleteResult *stats);

/* ginpostinglist.c */

extern GinPostingList *ginCompressPostingList(const ItemPointer ipd, int nipd,
											  int maxsize, int *nwritten);
extern int	ginPostingListDecodeAllSegmentsToTbm(GinPostingList *ptr, int totalsize, TIDBitmap *tbm);

extern ItemPointer ginPostingListDecodeAllSegments(GinPostingList *ptr, int len, int *ndecoded);
extern ItemPointer ginPostingListDecode(GinPostingList *ptr, int *ndecoded);
extern ItemPointer ginMergeItemPointers(ItemPointerData *a, uint32 na,
										ItemPointerData *b, uint32 nb,
										int *nmerged);

/*
 * 合并多个 gin 扫描的结果比较了许多项目指针，
 * 所以我们希望将其内联。
 */
static inline int
ginCompareItemPointers(ItemPointer a, ItemPointer b)
{
	uint64		ia = (uint64) GinItemPointerGetBlockNumber(a) << 32 | GinItemPointerGetOffsetNumber(a);
	uint64		ib = (uint64) GinItemPointerGetBlockNumber(b) << 32 | GinItemPointerGetOffsetNumber(b);

	if (ia == ib)
		return 0;
	else if (ia > ib)
		return 1;
	else
		return -1;
}

extern int	ginTraverseLock(Buffer buffer, bool searchMode);

#endif							/* GIN_PRIVATE_H */
